const puppeteer = require('puppeteer-core');
const findChrome = require('carlo/lib/find_chrome');
const generic = require('generic-pool');

let ChromePath = process.env.CHROME_PATH, // chrome 执行路径
    findChromePathPromise = null,
    browserLaunchPromise = null, // 浏览器初始化promise
    evictionRunIntervalMillis = 1000 * 60, // 每过多少时间检查状态
    idleTimeoutMillis = 1000 * 60 * 10, // page 对象多久未被使用后，销毁
    maxUseCount = 100, // page 最大复用上限
    maxUseTotalCount = 3000, // browser 最大复用次数，达到调用次数时，浏览器实例将重启
    max = 10, // 最大实例大小
    min = 1, // 最小
    incognito = true, // 隐私模式
    browserMaxRunTime = 1000 * 60 * 60 * 8;// browser 实例最大运行时间

const pool = generic.createPool({
    async create() {
        if (!browserLaunchPromise) {
            browserLaunchPromise = new Promise(async resolve => {
                const browser = await puppeteer.launch({
                    executablePath: await executablePath(),
                    args: [
                        '–-disable-gpu',
                        '–-disable-dev-shm-usage',
                        '–-disable-setuid-sandbox',
                        '–-no-first-run',
                        // '–process-per-tab',
                        '–single-process',
                        '--no-sandbox',
                        '–-no-zygote',
                    ]
                });
                browser.startTime = Date.now();
                browser.pagesLength = 0;
                browser.useTotalCount = 0;
                resolve(browser);
                console.log('创建了一个新的浏览器实例');
            });
        }
        const browser = await browserLaunchPromise;
        const page = await createPage(browser);

        browser.pagesLength += 1;
        page.useCount = 0;
        page.on('error', function () {
            page.isErrord = true;
        });
        page.jsError = [];
        page.on('pageerror', function (err) {
            page.jsError.push(String(err));
        });
        return page;
    },

    /**
     * @param page {Page}
     * @return {Promise<void>}
     */
    async destroy(page) {
        try {
            const browser = page.browser();
            browser.pagesLength -= 1;
            await closePage(page);
        } catch (e) {
        }
    },

    /**
     * 校验实例是否还能呗复用
     * @param page {Page}
     * @return {Promise<boolean>}
     */
    async validate(page) {
        const browser = page.browser();
        const {closed, startTime, useTotalCount} = browser;

        // 实例关闭
        if (closed) {
            return false;
        }

        const isConnected = browser.isConnected();
        const runTime = Date.now() - startTime;

        //  实例断开链接      运行时间太长                     实例使用太多次
        if (!isConnected || runTime >= browserMaxRunTime || useTotalCount >= maxUseTotalCount) {
            closeBrowser(browser)
            return false;
        }

        const {isErrord, useCount} = page;
        // 页面已被关闭          页面崩溃          页面使用次数超过限制
        if (page.isClosed() || isErrord || useCount >= maxUseCount) {
            return false;
        }

        return true; // 实例可正常使用
    }
}, {
    max, min,
    testOnBorrow: true, // 启用validate 校验
    autostart: false, // 是否自动启动池
    evictionRunIntervalMillis,
    idleTimeoutMillis
});

// 获取浏览器执行文件路径
if (!ChromePath) {
    findChromePathPromise = new Promise(async resolve => {
        try {
            ChromePath = (await findChrome({})).executablePath;
            if (!ChromePath) {
                exit();
            }
        } catch (e) {
            exit();
        }

        function exit() {
            console.error('无法找到 Chrome 执行路径');
            process.exit(1);
        }

        resolve(ChromePath);
    });
}

async function executablePath() {
    return ChromePath || findChromePathPromise;
}

exports.incognito = incognito; // 隐私模式

exports.executablePath = executablePath;

exports.createPage = createPage;

async function createPage(browser) {
    if (incognito) { // 创建一个匿名的浏览器上下文，它将忽略缓存和cookie
        const incognitoBrowser = await browser.createIncognitoBrowserContext();
        return incognitoBrowser.newPage();
    }
    return browser.newPage();
}

exports.closePage = closePage;

async function closePage(page) {
    if (incognito) {
        await page.browserContext().close();
    } else {
        await page.close();
    }
}

/**
 * @param browser {Browser}
 */
function closeBrowser(browser) {
    if (browser.closed) {
        return;
    }
    browserLaunchPromise = null;
    browser.closed = true;
    browser.on('targetdestroyed', function () {
        if (!browser.pagesLength || browser.pagesLength < 0) {
            browser.close();
            console.log('浏览器实例关闭');
        }
    })
}

/**
 * 创建一个页面实例
 * @return {Promise<Page>}
 */
exports.newPage = async function () {
    const page = await pool.acquire();
    const browser = page.browser();
    browser.useTotalCount += 1;
    page.useCount += 1;

    return page;
};

exports.proxyBrowser = async function (browserWSEndpoint) {
    return puppeteer.connect({browserWSEndpoint});
};

// 释放该实例，使得该页面实例可被其它程序重复使用
exports.release = function (page) {
    pool.release(page);
}

process.on('beforeExit', exitHandler);
process.on('exit', exitHandler);
process.on('SIGINT', exitHandler);

async function exitHandler() {
    try {
        await pool.drain();
        await pool.clear();
    } catch (e) {
    } finally {
        process.exit(0);
    }
}
